#pragma once

#include <cstdint>
#include <exception>
#include <iostream>
#include <unordered_map>
#include <vector>
#include <mockturtle/mockturtle.hpp>

template <typename TT>
inline TT boolean_dif(const TT &tt, uint8_t var_index)
{
  auto var0 = cofactor0(tt, var_index);
  auto var1 = cofactor1(tt, var_index);
  return binary_xor(var0, var1);
}

template <class TT>
int m5aj_bottom_decomposition(const TT &tt, uint32_t var_index0, uint32_t var_index1, uint32_t var_index2, uint32_t var_index3, uint32_t var_index4, TT *func = nullptr)
{
  static_assert(kitty::is_complete_truth_table<TT>::value, "Can only be applied on complete truth tables.");

  const auto dif_var0123 = boolean_dif(boolean_dif(boolean_dif(boolean_dif(tt, var_index0), var_index1), var_index2), var_index3);
  const auto dif_var0124 = boolean_dif(boolean_dif(boolean_dif(boolean_dif(tt, var_index0), var_index1), var_index2), var_index4);
  const auto dif_var0234 = boolean_dif(boolean_dif(boolean_dif(boolean_dif(tt, var_index0), var_index2), var_index3), var_index4);
  const auto dif_var0134 = boolean_dif(boolean_dif(boolean_dif(boolean_dif(tt, var_index0), var_index1), var_index3), var_index4);
  const auto dif_var1234 = boolean_dif(boolean_dif(boolean_dif(boolean_dif(tt, var_index0), var_index1), var_index2), var_index3);

  if (!equal(dif_var0123, dif_var0124) | !equal(dif_var0123, dif_var0234) | !equal(dif_var0123, dif_var0134) | !equal(dif_var0123, dif_var1234) | !is_const0(~dif_var0123))
  {
    return -1;
  }

  const auto f001 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index0), var_index1), var_index2), var_index3), var_index4);
  const auto f002 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index0), var_index2), var_index1), var_index3), var_index4);
  const auto f003 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index0), var_index3), var_index2), var_index1), var_index4);
  const auto f004 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index0), var_index4), var_index2), var_index3), var_index1);

  const auto f012 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index1), var_index2), var_index0), var_index3), var_index4);
  const auto f013 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index1), var_index3), var_index0), var_index2), var_index4);
  const auto f014 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index1), var_index4), var_index0), var_index3), var_index2);

  const auto f023 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index2), var_index3), var_index0), var_index1), var_index4);
  const auto f024 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index2), var_index4), var_index0), var_index3), var_index1);

  const auto f034 = cofactor0(cofactor0(cofactor0(boolean_dif(boolean_dif(tt, var_index3), var_index4), var_index0), var_index1), var_index2);

  const auto num_pairs =
      static_cast<uint32_t>(is_const0(f001)) +
      static_cast<uint32_t>(is_const0(f002)) +
      static_cast<uint32_t>(is_const0(f003)) +
      static_cast<uint32_t>(is_const0(f004)) +
      static_cast<uint32_t>(is_const0(f012)) +
      static_cast<uint32_t>(is_const0(f013)) +
      static_cast<uint32_t>(is_const0(f014)) +
      static_cast<uint32_t>(is_const0(f023)) +
      static_cast<uint32_t>(is_const0(f024)) +
      static_cast<uint32_t>(is_const0(f034));

  const auto num_pairs_1 =
      static_cast<uint32_t>(is_const0(~f001)) +
      static_cast<uint32_t>(is_const0(~f002)) +
      static_cast<uint32_t>(is_const0(~f003)) +
      static_cast<uint32_t>(is_const0(~f004)) +
      static_cast<uint32_t>(is_const0(~f012)) +
      static_cast<uint32_t>(is_const0(~f013)) +
      static_cast<uint32_t>(is_const0(~f014)) +
      static_cast<uint32_t>(is_const0(~f023)) +
      static_cast<uint32_t>(is_const0(~f024)) +
      static_cast<uint32_t>(is_const0(~f034));

  if (num_pairs + num_pairs_1 != 10u)
  {
    return -1;
  }

  if (num_pairs == 10u)
  {
    auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index1), var_index2), var_index3);
    auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index0), var_index1), var_index2), var_index3), var_index4);
    kitty::dynamic_truth_table tt_1(difh_g.num_vars());
    if (func)
    {
      *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
    }
    return 0;
  }
  if (num_pairs == 4u)
  {
    if (is_const0(f001) && is_const0(f003) && is_const0(f002) && is_const0(f004))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index1), var_index2), var_index3);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index0), var_index1), var_index2), var_index3), var_index4);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 1;
    }
    if (is_const0(f001) && is_const0(f012) && is_const0(f013) && is_const0(f014))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index2), var_index3), var_index4);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index1), var_index0), var_index2), var_index3), var_index4);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 2;
    }
    if (is_const0(f012) && is_const0(f023) && is_const0(f024) && is_const0(f002))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index3), var_index4), var_index0);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index2), var_index0), var_index1), var_index3), var_index4);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 3;
    }
    if (is_const0(f013) && is_const0(f003) && is_const0(f023) && is_const0(f034))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index4), var_index0), var_index1);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index3), var_index0), var_index2), var_index1), var_index4);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 4;
    }
    if (is_const0(f014) && is_const0(f034) && is_const0(f024) && is_const0(f004))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index0), var_index1), var_index2);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index4), var_index0), var_index2), var_index3), var_index1);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 5;
    }
  }
  if (num_pairs == 1u)
  {
    if (is_const0(f001))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index2), var_index3), var_index4);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index4), var_index0), var_index2), var_index3), var_index1);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 6;
    }
    if (is_const0(f002))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index1), var_index3), var_index4);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index4), var_index0), var_index3), var_index2), var_index1);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 7;
    }
    if (is_const0(f003))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index1), var_index2), var_index4);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index4), var_index0), var_index2), var_index3), var_index1);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 8;
    }
    if (is_const0(f004))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index1), var_index3), var_index2);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index1), var_index4), var_index2), var_index3), var_index0);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 9;
    }
    if (is_const0(f012))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index0), var_index3), var_index4);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index3), var_index4), var_index2), var_index1), var_index0);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 10;
    }
    if (is_const0(f013))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index0), var_index2), var_index4);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index0), var_index4), var_index1), var_index2), var_index3);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 11;
    }
    if (is_const0(f014))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index0), var_index2), var_index3);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index0), var_index4), var_index2), var_index1), var_index3);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 12;
    }
    if (is_const0(f023))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index0), var_index1), var_index4);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index0), var_index4), var_index2), var_index1), var_index3);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 13;
    }
    if (is_const0(f024))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index0), var_index1), var_index3);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index0), var_index4), var_index1), var_index2), var_index3);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 14;
    }
    if (is_const0(f034))
    {
      auto h_g_bar = cofactor0(cofactor0(cofactor0(tt, var_index0), var_index2), var_index1);
      auto difh_g = cofactor1(cofactor1(cofactor0(cofactor0(boolean_dif(tt, var_index0), var_index4), var_index2), var_index1), var_index3);
      kitty::dynamic_truth_table tt_1(difh_g.num_vars());
      if (func)
      {
        *func = kitty::binary_xor(mux_var(var_index0, difh_g, tt_1), h_g_bar);
      }
      return 15;
    }
  }
  return -1;
}
